/* More.cpp
Michael Zahniser, 2008

Function definitions for the More class.
*/

#include "More.h"

#include <ncurses.h>
#include <algorithm>
#include <sstream>

using std::find;
using std::ostringstream;



More::More(const string &text)
	: text(text)
{
}



void More::Run()
{
	initscr();
	cbreak();
	keypad(stdscr, true);
	noecho();

	// Reserve the last row for user input of commands.
	getmaxyx(stdscr, rows, columns);
	--rows;

	// Get iterators to the first and last visible rows.
	first = text.begin();
	last = first;
	for(int i = 0; i < rows; ++i)
		ScrollDown(last);
	
	while(true)
	{
		Draw();
		
		int key = getch();
		if(key == 'q' || (last == text.end() && key == ' '))
			break;
		
		int scroll = 0;
		switch(key)
		{
		case KEY_DOWN:
		case 'v':
		case 'j':
		case '\n':
			scroll = 1;
			break;
		case KEY_UP:
		case 'y':
		case 'k':
			scroll = -1;
			break;
		case 'd':
			scroll = rows / 2;
			break;
		case 'u':
			scroll = -rows / 2;
			break;
		case ' ':
		case 'f':
			scroll = rows;
			break;
		case 'b':
			scroll = -rows;
			break;
		}

		// Scroll down or up.
		for(int i = 0; i < scroll && ScrollDown(last); ++i)
			ScrollDown(first);
		for(int i = 0; i < -scroll && ScrollUp(first); ++i)
			ScrollUp(last);
	}

	endwin();
}



// Draw the portion of the text that is visible.
void More::Draw() const
{
	string::const_iterator it = first;
	
	for(int row = 0; row < rows; ++row)
	{
		string::const_iterator begin = it;
		it = find(it, last, '\n');
		
	    mvaddstr(row, 0, string(begin, it).c_str());
	    clrtoeol();
		
		if(it == last)
			break;
		
		++it;
	}
	
	// Write the position, as a percentage.
	ostringstream percent;
	if(first == text.begin())
	{
		if(last == text.end())
			percent << "ALL ";
		else
			percent << "TOP ";
	}
	else if(last == text.end())
		percent << "END ";
	
	percent << "(" << (100 * (last - text.begin()) + text.size() / 2) / text.size() << "%)";
	/*
	// Show the percentage, with the top page being 0% and the bottom, 100%.
	size_t divisor = text.size() - (last - first);
	if(!divisor)
		percent << "(ALL)";
	else
		percent << "(" << (100 * (first - text.begin()) + divisor / 2) / divisor << "%)";
	*/

	attron(A_REVERSE);
	mvaddstr(rows, 0, percent.str().c_str());
	clrtoeol();
	attroff(A_REVERSE);
}


	
// Attempt to scroll the given iterator up. If it cannot be done
// because the iterator is at the beginning of the text, return false.
bool More::ScrollUp(string::const_iterator &it) const
{
	if(it == text.begin())
		return false;
	
	// Find the previous line break. Assume *(it - 1) is '\n'.
	--it;

	// Find the previous newline. This can't be done cleanly with find and
	// a reverse iterator, so do it manually:
	while(--it >= text.begin())
		if(*it == '\n')
			break;
	
	// The above loop ended either because it == text.begin() - 1, or because
	// *it == '\n'. Either way, we want to:
	++it;

	return true;
}



// Attempt to scroll the given iterator up. If it cannot be done
// because the iterator is at the end of the text, return false.
bool More::ScrollDown(string::const_iterator &it) const
{
	if(it == text.end())
		return false;
	
	// Find the next line break.
	it = find(it, text.end(), '\n');
	// Set the iterator to right after the newline we found.
	if(it != text.end())
		++it;
	
	return true;
}
